// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <GLES2/gl2extchromium.h>

#include <cmath>

#include "base/basictypes.h"
#include "base/bind.h"
#include "base/location.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/thread_task_runner_handle.h"
#include "gpu/command_buffer/tests/gl_manager.h"
#include "gpu/command_buffer/tests/gl_test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace gpu {

class GLReadbackTest : public testing::Test {
protected:
    void SetUp() override { gl_.Initialize(GLManager::Options()); }

    void TearDown() override { gl_.Destroy(); }

    static void WaitForQueryCallback(int q, base::Closure cb)
    {
        unsigned int done = 0;
        glGetQueryObjectuivEXT(q, GL_QUERY_RESULT_AVAILABLE_EXT, &done);
        if (done) {
            cb.Run();
        } else {
            base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
                FROM_HERE, base::Bind(&WaitForQueryCallback, q, cb),
                base::TimeDelta::FromMilliseconds(3));
        }
    }

    void WaitForQuery(int q)
    {
        base::RunLoop run_loop;
        WaitForQueryCallback(q, run_loop.QuitClosure());
        run_loop.Run();
    }

    GLManager gl_;
};

TEST_F(GLReadbackTest, ReadPixelsWithPBOAndQuery)
{
    const GLint kBytesPerPixel = 4;
    const GLint kWidth = 2;
    const GLint kHeight = 2;

    GLuint b, q;
    glClearColor(0.0, 0.0, 1.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    glGenBuffers(1, &b);
    glGenQueriesEXT(1, &q);
    glBindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, b);
    glBufferData(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM,
        kWidth * kHeight * kBytesPerPixel,
        NULL,
        GL_STREAM_READ);
    glBeginQueryEXT(GL_ASYNC_PIXEL_PACK_COMPLETED_CHROMIUM, q);
    glReadPixels(0, 0, kWidth, kHeight, GL_RGBA, GL_UNSIGNED_BYTE, 0);
    glEndQueryEXT(GL_ASYNC_PIXEL_PACK_COMPLETED_CHROMIUM);
    glFlush();
    WaitForQuery(q);

    // TODO(hubbe): Check that glMapBufferCHROMIUM does not block here.
    unsigned char* data = static_cast<unsigned char*>(
        glMapBufferCHROMIUM(
            GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM,
            GL_READ_ONLY));
    EXPECT_TRUE(data);
    EXPECT_EQ(data[0], 0); // red
    EXPECT_EQ(data[1], 0); // green
    EXPECT_EQ(data[2], 255); // blue
    glUnmapBufferCHROMIUM(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM);
    glBindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, 0);
    glDeleteBuffers(1, &b);
    glDeleteQueriesEXT(1, &q);
    GLTestHelper::CheckGLError("no errors", __LINE__);
}

static float HalfToFloat32(uint16 value)
{
    int32 s = (value >> 15) & 0x00000001;
    int32 e = (value >> 10) & 0x0000001f;
    int32 m = value & 0x000003ff;

    if (e == 0) {
        if (m == 0) {
            uint32 result = s << 31;
            return bit_cast<float>(result);
        } else {
            while (!(m & 0x00000400)) {
                m <<= 1;
                e -= 1;
            }

            e += 1;
            m &= ~0x00000400;
        }
    } else if (e == 31) {
        if (m == 0) {
            uint32 result = (s << 31) | 0x7f800000;
            return bit_cast<float>(result);
        } else {
            uint32 result = (s << 31) | 0x7f800000 | (m << 13);
            return bit_cast<float>(result);
        }
    }

    e = e + (127 - 15);
    m = m << 13;

    uint32 result = (s << 31) | (e << 23) | m;
    return bit_cast<float>(result);
}

static GLuint CompileShader(GLenum type, const char* data)
{
    const char* shaderStrings[1] = { data };

    GLuint shader = glCreateShader(type);
    glShaderSource(shader, 1, shaderStrings, NULL);
    glCompileShader(shader);

    GLint compile_status = 0;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &compile_status);
    if (compile_status != GL_TRUE) {
        glDeleteShader(shader);
        shader = 0;
    }

    return shader;
}

TEST_F(GLReadbackTest, ReadPixelsFloat)
{
    const GLsizei kTextureSize = 4;
    const GLfloat kDrawColor[4] = { -10.9f, 0.5f, 10.5f, 100.12f };
    const GLfloat kEpsilon = 0.01f;

    struct TestFormat {
        GLint format;
        GLint type;
        uint32 comp_count;
    };
    TestFormat test_formats[4];
    size_t test_count = 0;
    const char* extensions = reinterpret_cast<const char*>(
        glGetString(GL_EXTENSIONS));
    if (strstr(extensions, "GL_OES_texture_half_float") != NULL) {
        TestFormat rgb16f = { GL_RGB, GL_HALF_FLOAT_OES, 3 };
        test_formats[test_count++] = rgb16f;

        TestFormat rgba16f = { GL_RGBA, GL_HALF_FLOAT_OES, 4 };
        test_formats[test_count++] = rgba16f;
    }
    if (strstr(extensions, "GL_OES_texture_float") != NULL) {
        TestFormat rgb32f = { GL_RGB, GL_FLOAT, 3 };
        test_formats[test_count++] = rgb32f;

        TestFormat rgba32f = { GL_RGBA, GL_FLOAT, 4 };
        test_formats[test_count++] = rgba32f;
    }

    const char* vs_source = "precision mediump float;\n"
                            "attribute vec4 a_position;\n"
                            "void main() {\n"
                            "  gl_Position =  a_position;\n"
                            "}\n";

    GLuint vertex_shader = CompileShader(GL_VERTEX_SHADER, vs_source);
    ASSERT_NE(vertex_shader, GLuint(0));

    const char* fs_source = "precision mediump float;\n"
                            "uniform vec4 u_color;\n"
                            "void main() {\n"
                            "  gl_FragColor = u_color;\n"
                            "}\n";

    GLuint fragment_shader = CompileShader(GL_FRAGMENT_SHADER, fs_source);
    ASSERT_NE(fragment_shader, GLuint(0));

    GLuint program = glCreateProgram();
    glAttachShader(program, vertex_shader);
    glDeleteShader(vertex_shader);
    glAttachShader(program, fragment_shader);
    glDeleteShader(fragment_shader);
    glLinkProgram(program);

    GLint link_status = 0;
    glGetProgramiv(program, GL_LINK_STATUS, &link_status);
    if (link_status != GL_TRUE) {
        glDeleteProgram(program);
        program = 0;
    }
    ASSERT_NE(program, GLuint(0));

    EXPECT_EQ(glGetError(), GLenum(GL_NO_ERROR));

    float quad_vertices[] = {
        -1.0, -1.0,
        1.0, -1.0,
        1.0, 1.0,
        -1.0, 1.0
    };

    GLuint vertex_buffer;
    glGenBuffers(1, &vertex_buffer);
    glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
    glBufferData(
        GL_ARRAY_BUFFER, sizeof(quad_vertices),
        reinterpret_cast<void*>(quad_vertices), GL_STATIC_DRAW);

    GLint position_location = glGetAttribLocation(program, "a_position");
    glVertexAttribPointer(
        position_location, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), NULL);
    glEnableVertexAttribArray(position_location);

    glUseProgram(program);
    glUniform4fv(glGetUniformLocation(program, "u_color"), 1, kDrawColor);

    EXPECT_EQ(glGetError(), GLenum(GL_NO_ERROR));

    for (size_t ii = 0; ii < test_count; ++ii) {
        GLuint texture_id = 0;
        glGenTextures(1, &texture_id);
        glBindTexture(GL_TEXTURE_2D, texture_id);
        glTexImage2D(
            GL_TEXTURE_2D, 0, test_formats[ii].format, kTextureSize, kTextureSize,
            0, test_formats[ii].format, test_formats[ii].type, NULL);

        GLuint framebuffer = 0;
        glGenFramebuffers(1, &framebuffer);
        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
        glFramebufferTexture2D(
            GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_id, 0);

        EXPECT_EQ(glGetError(), GLenum(GL_NO_ERROR));

        // Make sure this floating point framebuffer is supported
        if (glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) {
            // Check if this implementation supports reading floats back from this
            // framebuffer
            GLint read_format = 0;
            glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &read_format);
            GLint read_type = 0;
            glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &read_type);

            EXPECT_EQ(glGetError(), GLenum(GL_NO_ERROR));

            if ((read_format == GL_RGB || read_format == GL_RGBA) && read_type == test_formats[ii].type) {
                glClear(GL_COLOR_BUFFER_BIT);
                glDrawArrays(GL_TRIANGLE_FAN, 0, 4);

                uint32 read_comp_count = 0;
                switch (read_format) {
                case GL_RGB:
                    read_comp_count = 3;
                    break;
                case GL_RGBA:
                    read_comp_count = 4;
                    break;
                }

                switch (read_type) {
                case GL_HALF_FLOAT_OES: {
                    scoped_ptr<GLushort[]> buf(
                        new GLushort[kTextureSize * kTextureSize * read_comp_count]);
                    glReadPixels(
                        0, 0, kTextureSize, kTextureSize, read_format, read_type,
                        buf.get());
                    EXPECT_EQ(glGetError(), GLenum(GL_NO_ERROR));
                    for (uint32 jj = 0; jj < kTextureSize * kTextureSize; ++jj) {
                        for (uint32 kk = 0; kk < test_formats[ii].comp_count; ++kk) {
                            EXPECT_LE(
                                std::abs(HalfToFloat32(buf[jj * read_comp_count + kk]) - kDrawColor[kk]),
                                std::abs(kDrawColor[kk] * kEpsilon));
                        }
                    }
                    break;
                }
                case GL_FLOAT: {
                    scoped_ptr<GLfloat[]> buf(
                        new GLfloat[kTextureSize * kTextureSize * read_comp_count]);
                    glReadPixels(
                        0, 0, kTextureSize, kTextureSize, read_format, read_type,
                        buf.get());
                    EXPECT_EQ(glGetError(), GLenum(GL_NO_ERROR));
                    for (uint32 jj = 0; jj < kTextureSize * kTextureSize; ++jj) {
                        for (uint32 kk = 0; kk < test_formats[ii].comp_count; ++kk) {
                            EXPECT_LE(
                                std::abs(buf[jj * read_comp_count + kk] - kDrawColor[kk]),
                                std::abs(kDrawColor[kk] * kEpsilon));
                        }
                    }
                    break;
                }
                }
            }
        }

        glDeleteFramebuffers(1, &framebuffer);
        glDeleteTextures(1, &texture_id);
    }

    glDeleteBuffers(1, &vertex_buffer);
    glDeleteProgram(program);
}

} // namespace gpu
